在 Active Record 裡,有一個滿常見的功能,Enum,這功能該如何使用,今天就來點 Enum 吧!
Enum(Enumeration的縮寫)稱為列舉,是一種程式設計中常見的資料型別,
用於定義一組具有固定名稱的整數常數。
這些名稱通常用作代表特定狀態、選項、或類別的符號,使我們在程式上更易於閱讀、理解和維護。
讓我們來看看 Rails 裡該如何使用 Enum 吧!
在 Rails 中,Enum(列舉)是一個用於定義 Model 屬性的機制。
可以使用 Enum 來將整數映射到易於理解的名稱(自定義名稱),以增強代碼的可讀性。
首先,我們已經建立好 Order Model,
現在我想針對一張訂單可能會有的狀態新增一個欄位到 orders 資料表上。
一張訂單可能有這四種狀態:pending、shipped、delivered 和 canceled,
可以在建立狀態欄位上使用整數的資料型態來設置:
rails g migration AddStatusToOrder status:integer
點進去剛剛新增的 migration 檔案:
class AddStatusToOrder < ActiveRecord::Migration[7.0]
  def change
    add_column :orders, :status, :integer
  end
end
如果想設定預設值為 0,可以加上 default: 0
class AddStatusToOrder < ActiveRecord::Migration[7.0]
  def change
    add_column :orders, :status, :integer, default: 0
  end
end
接著,我們到 Order Model,使用 Enum 來定義狀態:
# app/models/order.rb
class Order < ApplicationRecord
  enum :status, [:pending, :shipped, :delivered, :canceled]
end
%i() format# app/models/order.rb
class Order < ApplicationRecord
  enum :status, %i(pending shipped delivered canceled)
end
當我們使用陣列的方式去定義時,pending、shipped、delivered 和 canceled,
將會被映射到 0、1、2 和 3(陣列的第一個元素 index 為 0,以此類推)。
# app/models/order.rb
class Order < ApplicationRecord
  enum :status, {
    pending: 0,
    shipped: 1,
    delivered: 2,
    canceled: 3
  }
end
# app/models/order.rb
class Order < ApplicationRecord
  enum :status, {
    pending: '待處理',
    shipped: '已發貨',
    delivered: '已送達',
    canceled: '已取消'
  }
end
在使用上,比較建議用 Hash 的方式去定義,
因為當使用 Array 定義 Enum 時,資料庫會存取相應的 index 值,當之後要更改現有 Enum 的順序上會導致問題,因為資料庫裡的值可能與新的順序不符。
因此,當使用 Hash 定義,因為是自定義值,不是依賴於 index 值。當要更改 Enum 的順序時,只需更新 Enum 的 Hash 自定義值,而不會影響現有資料的整合性。
在 Model 定義好 Enum 之後,我們要如何使用呢?
statuses 方法透過剛剛的 Hash 定義,我們便可以用 Order.statuses 得到一個 Hash 如下:
# app/models/order.rb
class Order < ApplicationRecord
  enum :status, {
    pending: 0,
    shipped: 1,
    delivered: 2,
    canceled: 3
  }
end
Order.statuses
=> {"pending"=>0, "shipped"=>1, "delivered"=>2, "canceled"=>3}
Order.statuses[:pending] or Order.statuses["pending"]
=> 0
pending, shipped, delivered, canceled 方法透過 Order.pending, Order.shipped, Order.delivered, Order.canceled,
這些方法用於查詢具有相應狀態的訂單記錄。Order.pending 將返回所有狀態為 "pending" 的訂單。
Order.pending
  Order Load (0.3ms)  SELECT "orders".* FROM "orders" WHERE "orders"."status" = ?  [["status", 0]]
 => 
[#<Order:0x0000000110836420
  id: 3,
  order_name: "20231010001",
  created_at: Tue, 10 Oct 2023 06:17:08.953478000 UTC +00:00,
  updated_at: Tue, 10 Oct 2023 06:17:08.953478000 UTC +00:00,
  status: "pending">,
 #<Order:0x00000001108362e0
  id: 4,
  order_name: "20231010002",
  created_at: Tue, 10 Oct 2023 06:17:17.280539000 UTC +00:00,
  updated_at: Tue, 10 Oct 2023 06:17:17.280539000 UTC +00:00,
  status: "pending">,
 #<Order:0x00000001108361a0
  id: 5,
  order_name: "20231010001",
  created_at: Tue, 10 Oct 2023 07:12:39.204159000 UTC +00:00,
  updated_at: Tue, 10 Oct 2023 07:12:39.204159000 UTC +00:00,
  status: "pending">] 
status 方法status 方法是一個可以用在知道一個實體變數的狀態方法:
建立一張新的 order001 之後,可以用 order001.status,
剛剛因為有設定 default: 0,所以結果會是 "pending"。
order001 = Order.create(order_name: "20231010001")
order001.status
=> "pending" 
pending?, shipped?, delivered?, canceled? 方法透過自定義的名稱加上 ?,可以檢查是否為某種狀態:
order001 = Order.create(order_name: "20231010001")
order001.pending?
=> true 
order001.shipped?
=> false 
order001.delivered?
=> false 
order001.canceled?
=> false 
pending!, shipped!, delivered!, canceled! 方法透過自定義的名稱加上 !,可以更新狀態:
order001 = Order.create(order_name: "20231010001")
order001.shipped!
  TRANSACTION (0.1ms)  begin transaction
  Order Update (1.4ms)  UPDATE "orders" SET "updated_at" = ?, "status" = ? WHERE "orders"."id" = ?  [["updated_at", "2023-10-10 07:36:12.025293"], ["status", 1], ["id", 3]]
  TRANSACTION (1.2ms)  commit transaction
 => true
order001.shipped?
=> true
order001.pending?
=> false 
另外,我們也可以使用:order001.update(status: :shipped)
order001.update(status: :shipped)
  TRANSACTION (0.1ms)  begin transaction
  Order Update (0.8ms)  UPDATE "orders" SET "updated_at" = ?, "status" = ? WHERE "orders"."id" = ?  [["updated_at", "2023-10-10 07:38:30.268312"], ["status", 1], ["id", 3]]
  TRANSACTION (1.7ms)  commit transaction
 => true
order001.shipped?
=> true
order001.pending?
=> false 
但是相較 !,這寫法比較冗長,我比較想要快速一點點,通常我都會用第一種方式去更新!
在 Enum 還可以使用 prefix 和 suffix 選項來控制生成的方法的前綴和後綴,以避免命名衝突。
prefix(前綴):
class Order < ApplicationRecord
  enum status: { pending: 0, shipped: 1, delivered: 2, canceled: 3 }, prefix: true
end
生成的方法將具有前綴 status_: status_pending、status_shipped、status_delivered、status_canceled。
suffix(後綴):
class Order < ApplicationRecord
  enum status: { pending: 0, shipped: 1, delivered: 2, canceled: 3 }, suffix: true
end
生成的方法將具有後綴 _status: pending_status、shipped_status、delivered_status、canceled_status。
今天就到這啦!我們下篇見~
參考資料:
文章同步於個人部落格:Viiisit!(歡迎參觀 ୧ʕ•̀ᴥ•́ʔ୨)